package serial;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Scanner;
import java.util.TreeSet;

// /* Comment out when RXTX is absent
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;
// */

public class SerialInterface {
	/* STUBS - use these when RXTX is absent
	static boolean isNoSuchPortException(Throwable e) {
		return false;
	}

	private boolean connected;
	private TreeSet<String> possibleNames;
	
	public SerialInterface(SerialReaderListener... listener) {
		connected = false;
		possibleNames = new TreeSet<String>();
	}
	
	public void closeConnection() {
		connected = false;
	}
	
	public boolean isConnected() {
		return connected;
	}
	
	public void addPossibleName(String value) {
		if (value != null) possibleNames.add(value);
	}
	
	public Collection<String> getPossibleNames() {
		return Collections.unmodifiableCollection(possibleNames);
	}
	
	public String setup(String portName, String appName) throws SerialException {
		connected = true;
		return portName;
	}
	
	public void send(String msg, boolean disconnectAfter) throws SerialException {
		connected = !disconnectAfter;
	}
	// */
	
	// /* REAL CODE - use this when RXTX is present
	private static SerialPort initialize(String portName, String appName)
			throws SerialException {
		try {
			CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
			SerialPort serialPort = (SerialPort)portIdentifier.open(appName,2000);
			serialPort.setSerialPortParams(57600, SerialPort.DATABITS_8,
					SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
			return serialPort;
		} catch(NoSuchPortException e) {
			throw new SerialException(e);
		} catch(PortInUseException e) {
			throw new SerialException(e);
		} catch(UnsupportedCommOperationException e) {
			throw new SerialException(e);
		}
	}
	
	static boolean isNoSuchPortException(Throwable e) {
		return e instanceof NoSuchPortException;
	}

	private ArrayList<SerialReaderListener> readerListeners;
	private SerialReader reader;
	private SerialWriter writer;
	private SerialPort serialPort;
	private TreeSet<String> possibleNames;
	private EchoCounter echoes;
	private boolean isConnected;
	
	public SerialInterface(SerialReaderListener... listeners) {
		this.readerListeners = new ArrayList<SerialReaderListener>();
		for (SerialReaderListener srl: listeners) {
			readerListeners.add(srl);
		}
		//readerListeners.add(new Closer());
		
		possibleNames = new TreeSet<String>();
		serialPort = null;
		isConnected = false;
	}
	
	public boolean isConnected() {
		return isConnected;
	}

	public void closeConnection() {
		reader.halt();
		try {
			writer.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		serialPort.close();
		isConnected = false;
	}
	
	private class Closer implements SerialReaderListener {

		@Override
		public void halted() {
			try {
				System.out.println("Reader finished");
				writer.close();
				System.out.println("Writer closed");
				serialPort.close();
				System.out.println("Serial port closed");
				
			} catch (IOException e) {
				// ?????
				
			} finally {
				isConnected = false;
			}
		}

		@Override
		public void stringReceived(String input) {}

		@Override
		public void unplugged() {
			halted();
		}
		
	}
	
	public void addPossibleName(String value) {
		if (value != null) possibleNames.add(value);
	}
	
	public void loadPossibleNamesFrom(String filename) throws FileNotFoundException {
		Scanner s = new Scanner(new File(filename));
		while (s.hasNextLine()) {
			addPossibleName(s.nextLine());
		}
		s.close();
	}
	
	public Collection<String> getPossibleNames() {
		return Collections.unmodifiableCollection(possibleNames);
	}
	
	public String setup(String appName) throws SerialException {
		return setup(null, appName);
	}

	public String setup(String portName, String appName) throws SerialException {
		if (serialPort != null) {
			throw new SerialException("Serial port already configured");
		}
		try {
			if (portName == null) {
				for (String possibility : possibleNames) {
					try {
						serialPort = initialize(possibility, appName);
						if (serialPort != null) {
							portName = possibility;
							break;
						}
					} catch (Exception e) {
						// do nothing
					}
				}
				if (serialPort == null) {
					throw new SerialException(new NoSuchPortException());
				}
			} else {
				serialPort = initialize(portName, appName);
			}
	
			reader = new SerialReader(serialPort.getInputStream());
			for (SerialReaderListener readerListener: readerListeners) {
				reader.addSerialReaderListener(readerListener);
			}
			echoes = new EchoCounter();
			reader.addSerialReaderListener(echoes);
			reader.start();
	
			writer = new SerialWriter(serialPort.getOutputStream());
			isConnected = true;
			return portName;
		} catch (IOException e) {
			throw new SerialException(e);
		}
	}

	public void send(String msg, boolean disconnectAfter) throws SerialException {
		try {
			writer.send(msg);	
			echoes.msgSent(msg);
			// This does not yet work right.
			// if (disconnectAfter) {
			// 	echoes.addAllEchoesReceivedListener(new Disconnector());
			// }
		} catch (Exception e) {
			throw new SerialException(e);
		}
	}
	
	private class Disconnector implements AllEchoesReceivedListener {

		@Override
		public void allEchoesReceived() {
			closeConnection();
		}
		
	}
	
	// */
}

